package gui.mainWindow.finals;

import gui.mainWindow.MainWindow;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Observable;
import java.util.Observer;

import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.table.DefaultTableModel;

import tournamentData.ScoresComparator;
import tournamentData.TournamentData;
import tournamentTree.MatchData.Position;
import tournamentTree.TournamentNode;
import tournamentTree.TreeIdGenerator;

import com.mxgraph.model.mxCell;
import com.mxgraph.view.mxGraph;

import core.GuiComponentLabels;
import core.Person;
import dataBase.DbHolder;

public class FinalsController implements Observer {
	private final MainWindow window;
	private final int BORDER_X = 20;
	private final int BORDER_Y = 20;
	private final int Y_NODEDISTANCE = 50;
	private final int X_NODEDISTANCE = 150;
	private final int NODE_HEIGHT = 40;
	private final int NODE_WIDTH = 120;

	private double THIRDPLACE_UPPER_Y = 0;
	private double THIRDPLACE_LOWER_Y = 0;
	private double THIRDPLACE_CHILD_X = 0;

	private TournamentNode thirdPlaceTree; // reference to third place tree root
	private TournamentNode tournamentTree; // reference to tournament tree root

	public FinalsController(MainWindow mainWindow) {
		this.window = mainWindow;
		DbHolder.getInstance().getArcherData().addObserver(this);
		TournamentData.getInstance().addObserver(this);
		TournamentData.getInstance().getTargetAssignment().addObserver(this);
	}

	public void buildBowCathegoryDropDown() {
		Iterator < String > iterator = this.getParticipatingBowCathegories().iterator();
		ArrayList < String > bowCathegoryList = new ArrayList < String >();
		while (iterator.hasNext()) {
			bowCathegoryList.add(iterator.next());
		}
		ComboBoxModel bowCathegoryDropDownModel = new DefaultComboBoxModel(bowCathegoryList.toArray());
		this.window.getFinalsBowCathegoryDropDown().setModel(bowCathegoryDropDownModel);
	}

	private HashSet < String > getParticipatingBowCathegories() {
		HashSet < String > toReturn = new HashSet < String >();
		for (int i = 0; i < TournamentData.getInstance().getAllParticipants().length; i++) {
			Person person = DbHolder.getInstance().getArcherData().getById(TournamentData.getInstance().getAllParticipants() [i]);
			toReturn.add(person.getBowCathegory());
		}
		return toReturn;
	}

	public void buildTable() {
		// [Rang, ID, Nachname, Vorname, Verein, Score]
		DefaultTableModel model = new DefaultTableModel();
		model.setColumnIdentifiers(new String [] { GuiComponentLabels.RANK, GuiComponentLabels.ID, GuiComponentLabels.NAME, GuiComponentLabels.SURNAME, GuiComponentLabels.CLUB,
				GuiComponentLabels.SCORE });

		// insert players

		LinkedList < Integer > potentialParticipants = this.getPotentialFinalsParticipants(this.window.getFinalsBowCathegoryDropDown().getSelectedItem().toString());

		for (int i = 0; i < potentialParticipants.size(); i++) {
			Person person = DbHolder.getInstance().getArcherData().getById(potentialParticipants.get(i));
			int rank = i + 1;
			int id = potentialParticipants.get(i);
			String name = person.getName();
			String surName = person.getSurName();
			String club = person.getClub();
			int totalScore = TournamentData.getInstance().getScore(id).getTotalPoints();

			model.addRow(new Object [] { rank, id, name, surName, club, totalScore });
		}

		/**
		 * if there is just one player missing for the next bigger finals-size,
		 * add one freilos
		 */
		float rowTest = potentialParticipants.size() + 1;
		while (rowTest > 2) {
			rowTest /= 2;
		}
		if (rowTest == 2) {
			model.addRow(this.createFreilos());
		}

		this.window.getFinalsParticipantsTable().setModel(model);
	}

	private Object [] createFreilos() {
		return new Object [] { "", 0, "FREILOS", "", "", 0 };
	}

	private LinkedList < Integer > getPotentialFinalsParticipants(String bowCathegory) {
		LinkedList < Integer > toReturn = new LinkedList < Integer >();

		for (int i = 0; i < TournamentData.getInstance().getAllParticipants().length; i++) {
			Person person = DbHolder.getInstance().getArcherData().getById(TournamentData.getInstance().getAllParticipants() [i]);
			if (person.getBowCathegory().equals(bowCathegory)) {
				if (DbHolder.getInstance().getArcherData().hasPermission(bowCathegory, person.getAgeClass(), person.getGender())) {
					toReturn.add(TournamentData.getInstance().getAllParticipants() [i]);
				}
			}
		}

		/* sort list */
		Collections.sort(toReturn, ScoresComparator.getInstance());

		return toReturn;
	}

	@Override
	public void update(Observable arg0, Object arg1) {
		this.buildBowCathegoryDropDown();
	}

	private LinkedList < Integer > getFinalsParticipants() {
		LinkedList < Integer > toReturn = new LinkedList < Integer >();

		int totalRows = this.window.getFinalsParticipantsTable().getRowCount();
		int splitPosition = 1;
		while ((splitPosition * 2) <= totalRows) {
			splitPosition *= 2;
		}
		for (int i = 0; i < splitPosition; i++) {
			toReturn.add(Integer.parseInt(this.window.getFinalsParticipantsTable().getValueAt(i, 1).toString()));
		}
		return toReturn;
	}

	public void buildGraph() {
		this.resetGraph();
		TournamentNode tree = new TournamentNode(this.window.getFinalsBowCathegoryDropDown().getSelectedItem().toString(), this.getFinalsParticipants());
		this.tournamentTree = tree.getRoot();

		tree.getRoot().setParticipantsAtLeaves(this.getFinalsParticipants());
		tree.getRoot().insertFreilose();
		tree.getRoot().continueFreilose();
		this.checkForPlayability(tree.getRoot());
		this.insertTree(tree.getRoot());

		this.buildThirdPlaceGraph();

		this.setGraphLocked(true);
	}

	public void resetGraph() {
		this.setGraphLocked(false);
		mxGraph graph = this.window.getGraph();

		graph.getModel().beginUpdate();
		try {
			graph.selectAll();
			graph.resetEdges(graph.removeCells());
		} finally {
			// Updates the display
			graph.getModel().endUpdate();
		}
		TreeIdGenerator.getInstance().reset();
	}

	public void checkForPlayability(TournamentNode root) {
		if (!root.isLeaf()) {
			if (!root.getUpperChild().getMatchData().isEmpty() && !root.getLowerChild().getMatchData().isEmpty() && !root.getUpperChild().getMatchData().isFinished()
					&& !root.getLowerChild().getMatchData().isFinished() && !root.getUpperChild().getMatchData().isFreilos() && !root.getLowerChild().getMatchData().isFreilos()) {

				root.getUpperChild().getMatchData().setPlayable(true);
				root.getLowerChild().getMatchData().setPlayable(true);

				if (this.compareRecentScores(root.getUpperChild(), root.getLowerChild()) >= 0) {
					// means upper has higher score
					root.getUpperChild().getMatchData().setPosition(Position.LEFT);
					root.getLowerChild().getMatchData().setPosition(Position.RIGHT);
				} else {
					// means lower has higher score
					root.getUpperChild().getMatchData().setPosition(Position.RIGHT);
					root.getLowerChild().getMatchData().setPosition(Position.LEFT);
				}
			}
			this.checkForPlayability(root.getUpperChild());
			this.checkForPlayability(root.getLowerChild());
		}
	}

	/**
	 * @param upperNode
	 *            upper tree node
	 * @param lowerNode
	 *            lower tree node
	 * @return (upperNodeScore - lowerNodeScore) -> therefore positive value if
	 *         upperScore > lowerScore
	 */
	public int compareRecentScores(TournamentNode upperNode, TournamentNode lowerNode) {
		int upperNodeScore;
		int lowerNodeScore;
		/*
		 * with leaf nodes, get score from pre-finals
		 */
		if (upperNode.isLeaf() || lowerNode.isLeaf()) {
			upperNodeScore = TournamentData.getInstance().getScore(upperNode.getMatchData().getAssignedPlayerID()).getTotalPoints();
			lowerNodeScore = TournamentData.getInstance().getScore(lowerNode.getMatchData().getAssignedPlayerID()).getTotalPoints();
		}
		/*
		 * with non-leaf nodes, get score from previous match
		 */
		else {
			/**
			 * upper node
			 */

			/*
			 * if player comes from upper node
			 */
			if (upperNode.getMatchData().getAssignedPlayerID() == upperNode.getUpperChild().getMatchData().getAssignedPlayerID()) {
				upperNodeScore = upperNode.getUpperChild().getMatchData().getScore();
			}
			/*
			 * else he must come from lower node
			 */
			else {
				upperNodeScore = upperNode.getLowerChild().getMatchData().getScore();
			}

			/**
			 * lower node
			 */

			/*
			 * if player comes from upper node
			 */
			if (lowerNode.getMatchData().getAssignedPlayerID() == lowerNode.getUpperChild().getMatchData().getAssignedPlayerID()) {
				lowerNodeScore = lowerNode.getUpperChild().getMatchData().getScore();
			}
			/*
			 * else he must come from lower node
			 */
			else {
				lowerNodeScore = lowerNode.getLowerChild().getMatchData().getScore();
			}

		}
		return upperNodeScore - lowerNodeScore;
	}

	public Object insertTree(TournamentNode root) {

		mxCell parent;
		mxGraph graph = this.window.getGraph();

		graph.getModel().beginUpdate();
		try {
			TournamentNode node = root;
			double xCoordinate = this.getXCoordinate(node);
			double yCoordinate = this.getYCoordinate(node);

			if (node.getCurrentDepth() == 1) {
				this.THIRDPLACE_CHILD_X = xCoordinate;
				if (node.isUpperChild()) {
					this.THIRDPLACE_UPPER_Y = yCoordinate;
				}
				if (node.isLowerChild()) {
					this.THIRDPLACE_LOWER_Y = yCoordinate;
				}
			}

			parent = (mxCell) graph.insertVertex(graph.getDefaultParent(), null, node, xCoordinate, yCoordinate, this.NODE_WIDTH, this.NODE_HEIGHT);
			node.setGraphNode(parent);
			if (!node.isLeaf()) {
				Object upperChild = this.insertTree(node.getUpperChild());
				Object lowerChild = this.insertTree(node.getLowerChild());
				graph.insertEdge(graph.getDefaultParent(), null, null, upperChild, parent);
				graph.insertEdge(graph.getDefaultParent(), null, null, lowerChild, parent);
			}

		} finally {
			// Updates the display
			graph.getModel().endUpdate();
		}
		return parent;
	}

	private int getXCoordinate(TournamentNode node) {
		return this.BORDER_X + ((node.getMaxDepth() - node.getCurrentDepth()) * this.X_NODEDISTANCE);
	}

	private double getYCoordinate(TournamentNode node) {
		int positionInLayer = node.getPositionInLayer();
		int inverseDepth = node.getMaxDepth() - node.getCurrentDepth();
		double distance = Math.pow(2, inverseDepth);
		double offset = (Math.pow(2, inverseDepth) - 1) * (this.Y_NODEDISTANCE / 2);

		return this.BORDER_Y + ((positionInLayer * distance * this.Y_NODEDISTANCE) + offset);
	}

	private void setGraphLocked(boolean lock) {
		mxGraph graph = this.window.getGraph();
		graph.setCellsBendable(!lock);
		graph.setCellsDeletable(!lock);
		graph.setCellsDisconnectable(!lock);
		graph.setCellsLocked(lock);
		graph.setCellsMovable(!lock);
		graph.setCellsResizable(!lock);
		graph.setCellsSelectable(!lock);
		graph.setConnectableEdges(!lock);
		graph.setEdgeLabelsMovable(!lock);
		graph.setAllowDanglingEdges(!lock);
	}

	private void buildThirdPlaceGraph() {

		TournamentNode thirdPlaceTree = new TournamentNode(1, this.window.getFinalsBowCathegoryDropDown().getSelectedItem().toString());
		this.thirdPlaceTree = thirdPlaceTree.getRoot();

		this.insertThirdPlaceTree(thirdPlaceTree.getRoot());

	}

	public Object insertThirdPlaceTree(TournamentNode root) {

		mxCell parent;
		mxGraph graph = this.window.getGraph();

		TournamentNode node = root;
		double xCoordinate;
		double yCoordinate;

		double lower_y = this.THIRDPLACE_LOWER_Y + (2 * this.Y_NODEDISTANCE) + (2 * this.Y_NODEDISTANCE);
		double upper_y = this.THIRDPLACE_LOWER_Y + (2 * this.Y_NODEDISTANCE);

		/* root node */
		if (node.isRoot()) {
			xCoordinate = this.THIRDPLACE_CHILD_X + this.X_NODEDISTANCE;
			yCoordinate = ((lower_y) + (upper_y)) / 2;
		}

		/* else if node is upper child */
		else if (node.isUpperChild()) {
			xCoordinate = this.THIRDPLACE_CHILD_X;
			yCoordinate = upper_y;
		}

		/* else if node is lower child */
		else {
			xCoordinate = this.THIRDPLACE_CHILD_X;
			yCoordinate = lower_y;
		}

		graph.getModel().beginUpdate();
		try {
			parent = (mxCell) graph.insertVertex(graph.getDefaultParent(), null, node, xCoordinate, yCoordinate, this.NODE_WIDTH, this.NODE_HEIGHT);
			node.setGraphNode(parent);
			if (!node.isLeaf()) {
				Object upperChild = this.insertThirdPlaceTree(node.getUpperChild());
				Object lowerChild = this.insertThirdPlaceTree(node.getLowerChild());
				graph.insertEdge(graph.getDefaultParent(), null, null, upperChild, parent);
				graph.insertEdge(graph.getDefaultParent(), null, null, lowerChild, parent);
			}

		} finally {
			// Updates the display
			graph.getModel().endUpdate();
		}
		return parent;
	}

	public void insertPlayerToThirdPlaceTree(TournamentNode node) {
		if ((node.getID() == 3) || (node.getID() == 4)) {
			this.thirdPlaceTree.getUpperChild().getMatchData().setPlayer(node.getMatchData().getAssignedPlayerID());
		} else {
			this.thirdPlaceTree.getLowerChild().getMatchData().setPlayer(node.getMatchData().getAssignedPlayerID());
		}
		this.checkForPlayability(this.thirdPlaceTree.getRoot());
	}
}
